#!/usr/bin/env python

#
# OpenLookup - openlookup.py
#
# Copyright 2008-2009 Helsinki Institute for Information Technology
# and the authors.
#
# Authors: Ken Rimey <rimey@hiit.fi>
#

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import os
import time
import logging
from threading import Thread
from datetime import datetime

from openlookup.version import VERSION
from openlookup.engine.db import DB
from openlookup.server.sync import (
    SyncManager, SYNC_PORT, CONNECTION_LIMIT, RATE_LIMIT)
from openlookup.server.http import create_http_server, HTTP_PORT

LOGFILE = 'openlookup.log'

class Daemon(object):
    def __init__(self, filename, fast, threading,
                 http_host='', http_port=HTTP_PORT,
                 sync_host='', sync_port=SYNC_PORT):
        self.filename = filename
        self.fast = fast
        self.threading = threading
        self.http_host = http_host
        self.http_port = http_port
        self.sync_host = sync_host
        self.sync_port = sync_port
        self.start_time = time.time()

    def loop(self):
        DB(self.filename).close() # Ensure database exists.

        self.start_sync()
        self.start_http()
        try:
            sleep_forever()
        except KeyboardInterrupt:
            pass

    def start_sync(self):
        self.sync_mgr = SyncManager(self.filename, fast=self.fast, daemon=self)
        self.sync_mgr.create_server(self.sync_host, self.sync_port)

        self.sync_thread = Thread(target=self.sync_mgr.loop,
                                  name='sync-thread')
        self.sync_thread.setDaemon(True)
        self.sync_thread.start()

    def start_http(self):
        self.http_server = create_http_server(
            self.filename, self.fast, self.threading,
            self.http_host, self.http_port, daemon=self)

        self.http_thread = Thread(target=self.http_server.loop,
                                  name='http-thread')
        self.http_thread.setDaemon(True)
        self.http_thread.start()

    def get_name(self):
        db = DB(self.filename, create=False)
        return db.get_info('name', '')

    def gen_node_info(self):
        db = DB(self.filename, create=False)

        yield 'Name', db.get_info('name', None)
        yield 'Contact', db.get_info('contact', None)

        if hasattr(self, 'sync_mgr'):
            addresses = sorted(self.sync_mgr.outward_ip_addresses)
            yield 'IP address', ' / '.join(addresses)
            yield 'Peering port', self.sync_port

        yield 'Access port', self.http_port

        yield None, None

        if hasattr(self, 'sync_mgr'):
            yield 'Items/sec', self.sync_mgr.meter.get_rate()

        yield 'Items seen', db.get_mark()

        megabytes = os.path.getsize(self.filename) / 1000000.0
        yield 'Disk usage', '%.3f MB' % megabytes

        if hasattr(self, 'sync_mgr'):
            yield None, None

            count = len(self.sync_mgr.sessions)
            limit = db.get_info('connection_limit', CONNECTION_LIMIT)
            yield 'Connections', '%d / %d' % (count, limit)

            rate = db.get_info('rate_limit', RATE_LIMIT)
            yield 'Rate limit', '%.1f KB/sec in' % (rate / 1000.0)

            forward = db.get_info('forward', True)
            yield 'Forwarding', forward

        yield None, None

        yield 'V2 version', VERSION

        utc = datetime.utcfromtimestamp(self.start_time)
        yield 'Up since', utc.replace(microsecond=0).isoformat(' ') + ' UTC'

        utc = datetime.utcnow()
        yield 'Current time', utc.replace(microsecond=0).isoformat(' ') + ' UTC'

def sleep_forever():
    while True:
        time.sleep(30)

if __name__ == '__main__':
    from optparse import OptionParser
    from openlookup.engine.db import FILENAME
    from openlookup.lib.daemonize import daemonize

    parser = OptionParser()

    parser.add_option('--db', dest='filename', default=FILENAME,
                      help='location of database (default is %default)')
    parser.add_option('--sync_port', type='int', default=SYNC_PORT,
                      help='port to listen to for sync connections'
                           ' (default is %default)')
    parser.add_option('--http_port', type='int', default=HTTP_PORT,
                      help='port to listen to for HTTP and XML-RPC'
                           ' (default is %default)')
    parser.add_option('--port_offset', type='int', default=0,
                      help='offset to be added to all port numbers')
    parser.add_option('--sync_host', default='',
                      help='interface on which to listen for sync connections'
                           ' (default is all interfaces)')
    parser.add_option('--http_host', default='',
                      help='interface on which to listen for HTTP and XML-RPC'
                           ' (default is all interfaces)')
    parser.add_option('--safe', dest='fast',
                      action='store_false', default=True,
                      help="leave sqlite's synchronous mode turned on")
    parser.add_option('--nothreading', dest='threading',
                      action='store_false', default=True,
                      help="serve all HTTP requests from the same thread")
    parser.add_option('--daemon', dest='daemonize',
                      action='store_true', default=False,
                      help='become daemon and divert log messages'
                           ' to %s by default' % LOGFILE)
    parser.add_option('--debug', action='store_true', default=False,
                      help='log debugging messages too')
    parser.add_option('--log', dest='logfile', default=None,
                      help='send log messages to specified file')

    options, args = parser.parse_args()
    if args:
        parser.error('incorrect number of arguments')

    if options.debug:
        level = logging.DEBUG
    else:
        level = logging.INFO

    logfile = options.logfile
    if not logfile and options.daemonize:
        logfile = LOGFILE

    logging.basicConfig(level=level,
                        filename=logfile,
                        format='%(asctime)s %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')

    if options.daemonize:
        pid = daemonize()
        logging.info('-' * 50)
        logging.info('PID = %d', pid)

    daemon = Daemon(options.filename, options.fast, options.threading,
                    options.http_host, options.http_port + options.port_offset,
                    options.sync_host, options.sync_port + options.port_offset)
    daemon.loop()
